using System;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.ServiceModel;
using System.ServiceModel.Security.Tokens;
using System.Threading;
using Sinacor.Infra.Common.Caching;

namespace Sinacor.Infra.Common.Security.Authentication.ClientCredentials
{
    class CachedIssuedSecurityTokenProvider : SecurityTokenProvider, ICommunicationObject
    {
        IssuedSecurityTokenProvider _innerTokenProvider;
        IssuedTokenCache _cache;
        string _tokenId;
        string _host;

        public CachedIssuedSecurityTokenProvider(IssuedSecurityTokenProvider innerTokenProvider, string tokenId)
        {
            if (innerTokenProvider == null)
            {
                throw new ArgumentNullException("innerTokenProvider");
            }

            this._innerTokenProvider = innerTokenProvider;
            this._host = innerTokenProvider.TargetAddress.Uri.Host;
            this._tokenId = tokenId;
            this._cache = IssuedTokenCache.GetInstance();
        }

        protected override SecurityToken GetTokenCore(TimeSpan timeout)
        {
            GenericXmlSecurityToken token = null;

            if (!this._cache.TryGetToken(_tokenId,_host, out token))
            {
                token = (GenericXmlSecurityToken) this._innerTokenProvider.GetToken(timeout);
                this._cache.AddToken(token,_tokenId,_host);
            }

            return token;
        }

        protected override IAsyncResult  BeginGetTokenCore(TimeSpan timeout, AsyncCallback callback, object state)
        {
            GenericXmlSecurityToken token = null;

            if (this._cache.TryGetToken(_tokenId,_host, out token))
            {
                return new CompletedAsyncResult(token, callback, state);
            }
            else
            {
                return this._innerTokenProvider.BeginGetToken(timeout, callback, state);
            }
        }

        protected override SecurityToken EndGetTokenCore(IAsyncResult result)
        {
            CompletedAsyncResult caResult = result as CompletedAsyncResult;

            if (caResult != null)
            {
                return caResult.Token;
            }
            else
            {
                GenericXmlSecurityToken token = (GenericXmlSecurityToken) this._innerTokenProvider.EndGetToken(result);
                this._cache.AddToken(token, _tokenId, _host);
                return token;
            }
        }

        #region ICommunicationObject Members

        public void Abort()
        {
            this._innerTokenProvider.Abort();
        }

        public IAsyncResult BeginClose(TimeSpan timeout, AsyncCallback callback, object state)
        {
            return this._innerTokenProvider.BeginClose(timeout, callback, state);
        }

        public IAsyncResult BeginClose(AsyncCallback callback, object state)
        {
            return this._innerTokenProvider.BeginClose(callback, state);
        }

        public IAsyncResult BeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
        {
            return this._innerTokenProvider.BeginOpen(timeout, callback, state);
        }

        public IAsyncResult BeginOpen(AsyncCallback callback, object state)
        {
            return this._innerTokenProvider.BeginOpen(callback, state);
        }

        public void Close(TimeSpan timeout)
        {
            this._innerTokenProvider.Close(timeout);
        }

        public void Close()
        {
            this._innerTokenProvider.Close();
        }

        public event EventHandler Closed
        {
            add { this._innerTokenProvider.Closed += value; }
            remove { this._innerTokenProvider.Closed -= value; }
        }

        public event EventHandler Closing
        {
            add { this._innerTokenProvider.Closing += value; }
            remove { this._innerTokenProvider.Closing -= value; }
        }

        public void EndClose(IAsyncResult result)
        {
            this._innerTokenProvider.EndClose(result);
        }

        public void EndOpen(IAsyncResult result)
        {
            this._innerTokenProvider.EndOpen(result);
        }

        public event EventHandler Faulted
        {
            add { this._innerTokenProvider.Faulted += value; }
            remove { this._innerTokenProvider.Faulted -= value; }
        }

        public void Open(TimeSpan timeout)
        {
            this._innerTokenProvider.Open(timeout);
        }

        public void Open()
        {
            this._innerTokenProvider.Open();
        }

        public event EventHandler Opened
        {
            add { this._innerTokenProvider.Opened += value; }
            remove { this._innerTokenProvider.Opened -= value; }
        }

        public event EventHandler Opening
        {
            add { this._innerTokenProvider.Opening += value; }
            remove { this._innerTokenProvider.Opening -= value; }
        }

        public CommunicationState State
        {
            get { return this._innerTokenProvider.State; }
        }

        #endregion

        class CompletedAsyncResult : IAsyncResult
        {
            GenericXmlSecurityToken token;
            //AsyncCallback callback;
            object state;
            WaitHandle handle;
            object thisLock = new object();

            public CompletedAsyncResult(GenericXmlSecurityToken token, AsyncCallback callback, object state)
            {
                this.token = token;
                this.state = state;
                if (callback != null)
                {
                //    this.callback = callback;
                    callback(this);
                }
            }

            public GenericXmlSecurityToken Token
            {
                get
                {
                    return this.token;
                }
            }

            public object AsyncState
            {
                get { return this.state; }
            }

            public WaitHandle AsyncWaitHandle
            {
                get
                {
                    lock (thisLock)
                    {
                        if (this.handle == null)
                        {
                            this.handle = new ManualResetEvent(true);
                        }
                    }
                    return this.handle;
                }
            }

            public bool CompletedSynchronously
            {
                get { return true; }
            }

            public bool IsCompleted
            {
                get { return true; }
            }
        }
    }
}
